//
// Created by benxb on 2021/11/29.
//

#ifndef BENXDB_QUERY_H
#define BENXDB_QUERY_H

#include <string>
#include <utility>
#include <vector>
#include <type/value.h>
#include <tuple>
#include <common/config.h>

namespace benxdb {

struct Selection {
    std::string column_name;
    CompareSign comp;
    Value val;

    Selection(std::string column_name, CompareSign comp, const Value& val)
    : column_name(std::move(column_name)), comp(comp), val(val) {}
};

class QueryPhrase {
public:
    explicit QueryPhrase(std::string table_name) : table_name_(std::move(table_name)) {}
    ~QueryPhrase() = default;

    QueryPhrase* Select(std::vector<std::string> column_names) {
        query_column_ = std::move(column_names);
        return this;
    }

    QueryPhrase* Select(const std::string& column_name) {
        query_column_.push_back(column_name);
        return this;
    }

    QueryPhrase* Where(const std::string& column_name, CompareSign comp, const Value& val) {
        selections_.emplace_back(Selection(column_name, comp, val));
        return this;
    }

    std::vector<std::string> GetQueryColumns() {
        return query_column_;
    }

    std::vector<Selection> GetSelections() {
        return selections_;
    }

    std::string GetTableName() {
        return table_name_;
    }

    QueryPhrase* SetOr() {
        or_op_ = true;
        return this;
    }

    bool Or_Operation() const {
        return or_op_;
    }

private:
    std::string table_name_;
    std::vector<std::string> query_column_;
    // tuple elements are column_name, compare_sign and value
    // for example, x < 16, x is column_name, < is compare sign, 16 is value
    // only support AND now
    std::vector<Selection> selections_;
    bool or_op_ = false;
};

enum PointCloudQueryType {
    Rectangular,
    Circle,
    Polygon,
};

class PointCloudQuery {
public:
    explicit PointCloudQuery(std::string dataset) : dataset_(std::move(dataset)) {}
    ~PointCloudQuery() = default;

    PointCloudQuery* Select(std::vector<std::string> attributes_names) {
        query_attributes_ = std::move(attributes_names);
        return this;
    }

    PointCloudQuery* Select(const std::string& attribute_name) {
        query_attributes_.push_back(attribute_name);
        return this;
    }

    std::vector<std::string> GetQueryAttributes() {
        return query_attributes_;
    }

    std::string GetDatasetName() {
        return dataset_;
    }

    [[nodiscard]]virtual bool Satisfy(float x, float y) = 0;

    /**
     * Give a range and check if the chunk may have curtain values
     * @param min_x minimum x value
     * @param max_x maximum x value
     * @param min_y minimum y value
     * @param max_y maximum y value
     * @return
     */
    [[nodiscard]]virtual bool MaySatisfyIn(float min_x, float max_x, float min_y, float max_y) = 0;

private:
    std::string dataset_;
    std::vector<std::string> query_attributes_;
};

class CircleQuery : public PointCloudQuery {
public:
    explicit CircleQuery(std::string dataset) : PointCloudQuery(std::move(dataset)) {}
    ~CircleQuery() = default;

    CircleQuery* Around(float x, float y, float radius) {
        x_ = x;
        y_ = y;
        radius_ = radius;
        radius_square_ = radius*radius;
    }

    [[nodiscard]]bool MaySatisfyIn(float min_x, float max_x, float min_y, float max_y) override {
        if (min_x > x_+radius_ || max_x < x_-radius_) {
            return false;
        }
        if (min_y > y_+radius_ || max_y < y_-radius_) {
            return false;
        }
        return true;
    }

    [[nodiscard]] bool Satisfy(float x, float y) override  {
        return (x - x_)*(x - x_) + (y - y_)*(y - y_) <= radius_square_;
    }

private:
    float x_;
    float y_;
    float radius_;
    double radius_square_;
};

class RectangleQuery : public PointCloudQuery {
public:
    explicit RectangleQuery(std::string dataset) : PointCloudQuery(std::move(dataset)) {}
    ~RectangleQuery() = default;

    RectangleQuery* SetRectangle(float x_min, float x_max, float y_min, float y_max) {
        x_min_ = x_min;
        x_max_ = x_max;
        y_min_ = y_min;
        y_max_ = y_max;
    }

    [[nodiscard]]bool MaySatisfyIn(float min_x, float max_x, float min_y, float max_y) override {
        if (min_x > x_max_ || max_x < x_min_) {
            return false;
        }
        if (min_y > y_max_ || max_y < y_min_) {
            return false;
        }
        return true;
    }

    [[nodiscard]] bool Satisfy(float x, float y) override {
        return x > x_min_ && x < x_max_ && y > y_min_ && y < y_max_;
    }

private:
    float x_min_{};
    float y_min_{};
    float x_max_{};
    float y_max_{};
};

}


#endif //BENXDB_QUERY_H
